/*
 *  Copyright 2009 Lucas Nazário dos Santos
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package net.sourceforge.retriever.executor;

import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sourceforge.retriever.analyzer.Analyzer;
import net.sourceforge.retriever.feedback.Feedback;
import net.sourceforge.retriever.fetcher.Fetcher;
import net.sourceforge.retriever.fetcher.Resource;
import net.sourceforge.retriever.fetcher.dns.DNSResolver;
import net.sourceforge.retriever.filter.Filter;
import net.sourceforge.retriever.frontier.Frontier;
import net.sourceforge.retriever.frontier.FrontierUrl;

class URLParser implements Runnable {


	private final Frontier urlFrontier;

	private final Filter filter;

	private final Analyzer analyzer;

	private final Feedback feedback;

	private final DNSResolver dnsResolver;

	private final Object emptyFrontierLock;

	URLParser(final Frontier urlFrontier, final Filter filter, final Analyzer analyzer, final Feedback feedback, final Object emptyFrontierLock, final DNSResolver dnsResolver) {
		this.urlFrontier = urlFrontier;
		this.filter = filter;
		this.analyzer = analyzer;
		this.feedback = feedback;
		this.emptyFrontierLock = emptyFrontierLock;
		this.dnsResolver = dnsResolver;
	}

	public void run() {
		final List<FrontierUrl> frontierUrls = this.urlFrontier.dequeue();
		if (frontierUrls == null || frontierUrls.size() <= 0) return;

		boolean exceptionOccured = false;

		final Set<String> ips = new HashSet<String>();

		for (FrontierUrl frontierURL : frontierUrls) {
			final String url = frontierURL.getUrl();
			final String ip = frontierURL.getIp();

			ips.add(ip);

			try {
				if (!URLParser.this.filter.acceptBeforeFetch(url)) return;
				final Fetcher fetcher = Fetcher.create(new URL(url));
				final Resource resource = fetcher.fetch(this.isLastFrontierURL(frontierUrls, frontierURL));
				if (!this.filter.acceptAfterFetch(resource)) return;

				final Map<String, Object> additionalInfo = new HashMap<String, Object>();
				this.analyzer.analyze(resource, additionalInfo);
				frontierURL.setAdditionalInfo(additionalInfo);

				this.enqueue(resource.getInnerURLs(), frontierURL);
				frontierURL.setExceptionFetching(null);
			} catch (final Throwable t) {
				exceptionOccured = true;
				frontierURL.setExceptionFetching(t);
				URLParser.this.feedback.error(url, "Exception fetching the resource " + url, t);
			} finally {
				try {
					this.urlFrontier.enqueue(frontierURL);
				} finally {
					synchronized (emptyFrontierLock) {
						this.emptyFrontierLock.notifyAll();
					}
				}
			}

			try {
				Thread.sleep(frontierURL.getTimeToSleepInMillis());
			} catch (final InterruptedException e) {
				// TODO Log exception
			}
		}

		if (exceptionOccured) {
			for (FrontierUrl frontierURL : frontierUrls) {
				ips.add(frontierURL.getIp());
			}
		}

		for (String ip : ips) {
			this.urlFrontier.notifyFetchedIp(ip);
		}
	}

	private boolean isLastFrontierURL(final List<FrontierUrl> frontierURLs, final FrontierUrl frontierURL) {
		return (frontierURLs.indexOf(frontierURL) + 1) >= frontierURLs.size();
	}

	private void enqueue(final List<String> innerURLs, final FrontierUrl parent) {
		for (String innerURL : innerURLs) {
			if (!this.filter.acceptBeforeFetch(innerURL)) continue;

			if (this.filter.acceptBeforeFetch(innerURL)) {
				String ipInnerURL = this.dnsResolver.getIP(innerURL);

				final FrontierUrl frontierUrl = new FrontierUrl();
				frontierUrl.setUrl(innerURL);
				frontierUrl.setIp(ipInnerURL);
				frontierUrl.setParent(parent);

				this.urlFrontier.enqueue(frontierUrl);
			}
		}
	}
}